package com.seg.gradle

import com.github.kevinsawicki.http.HttpRequest
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskExecutionException

@CompileStatic
class RunScriptTask extends DefaultTask {
    final String localDir = '.gradle-rs'
    final String groovyScriptFileSuffix = '.groovy'

    String script
    String version = '1.0'
    Map<String, Object> params = [:]
    Closure callback

    boolean isPublish = false

    @TaskAction
    public void executeTask() {
        RunScriptPluginExtension ext = project.extensions.getByName('runscript') as RunScriptPluginExtension
        String runScriptMirror = ext.runScriptMirror

        if (isPublish) {
            doPublish(runScriptMirror, script, version)
            return
        }

        File scriptFile = getScriptFile(runScriptMirror, script, version)

        if (scriptFile == null || !scriptFile.exists())
            throw new TaskExecutionException(this, new RunScriptException('Target script not found : ' + script + ':' + version))

        def bind = new Binding()
        bind.setProperty('params', params)

        def config = new CompilerConfiguration()
        config.setClasspath(scriptFile.parentFile.absolutePath)
        def shell = new GroovyShell(bind, config)

        try {
            Object result = shell.evaluate(scriptFile)

            if (callback != null)
                callback.call(result)
        } catch (Exception e) {
            throw new TaskExecutionException(this, e)
        }
    }

    private PackageInfo getPackageInfo(String text) {
        def props = new Properties()
        props.load(new StringReader(text))
        String version = props.getProperty('version')
        String dependsStr = props.getProperty('depends')
        String filesStr = props.getProperty('files')

        List<Map<String, String>> depends = []
        // script2:1.0,script3:1.0
        if (dependsStr) {
            String[] arrOne = dependsStr.split(',')
            for (one in arrOne) {
                String[] arr = one.split(':')
                if (arr.size() == 2)
                    depends << [name: arr[0], version: arr[1]]
            }
        }

        List<String> files = ['package.properties', 'main.groovy']
        if (filesStr) {
            String[] arr = filesStr.split(',')
            for (one in arr)
                files << one
        }

        def packageInfo = new PackageInfo()

        packageInfo.version = version
        packageInfo.depends = depends
        packageInfo.files = files

        getLogger().info('package-info : ' + packageInfo)

        packageInfo
    }

    private boolean doUpload(String runScriptMirror, String localPathRelative, String script, String version, File file) throws TaskExecutionException {
        final String uploadUrl = runScriptMirror + '/upload'

        def req = HttpRequest.post(uploadUrl).part('script', script).
			part('version', version).
			part('file', localPathRelative).
			part('upload', file.name, 'application/octet-stream', file)

		getLogger().info('Publish script upload response : ' + req.body())
        req.ok()
    }

    private void doPublish(String runScriptMirror, String script, String version) throws TaskExecutionException {
        boolean isRemote = 'local' != runScriptMirror
        if (!isRemote)
            throw new TaskExecutionException(this, new RunScriptException('Publish script but mirror is not a http url : ' + runScriptMirror))

        String userHome = System.getProperty('user.home')
        String scriptBaseDir = userHome + '/' + localDir
        String scriptTargetDir = scriptBaseDir + '/' + script + '/' + version

        getLogger().info('script target dir : ' + scriptTargetDir)

        File dir = new File(scriptTargetDir)
        if (!dir.exists())
            throw new TaskExecutionException(this, new RunScriptException('Publish script but local script not exists : ' + scriptTargetDir))

        List<File> fileLocalList = []
        dir.eachFileRecurse { File it ->
            if (it.isFile()) {
                fileLocalList << it
            }
        }

		getLogger().info('Publish script files : ' + fileLocalList.collect{it.name})

        String dirPathAbs = dir.absolutePath
        for (file in fileLocalList) {
            String localPathRelative = file.absolutePath.substring(dirPathAbs.size())
            boolean isOk = doUpload(runScriptMirror, localPathRelative, script, version, file)

            if (!isOk)
                throw new TaskExecutionException(this, new RunScriptException('Publish script upload failed : ' + localPathRelative))

			getLogger().info('Publish script upload done : ' + localPathRelative)
        }
    }

    private void downloadFile(String file, String scriptTargetDir, String remoteUrlDir) throws TaskExecutionException {
        String localPath = scriptTargetDir + '/' + file
        String remotePath = remoteUrlDir + '/' + file

        try {
            File f = new File(localPath)
            f.parentFile.mkdirs()
            f.text = new URL(remotePath).text
        } catch (Exception ex) {
            throw new TaskExecutionException(this, new RunScriptException('Download script file failed : ' + remotePath, ex))
        }
    }

    // script -> a/b/c
    private File getScriptFile(String runScriptMirror, String script, String version) throws TaskExecutionException {
        boolean isRemote = 'local' != runScriptMirror

        String userHome = System.getProperty('user.home')
        String scriptBaseDir = userHome + '/' + localDir
        String scriptTargetDir = scriptBaseDir + '/' + script + '/' + version

        File f = new File(scriptTargetDir + '/main' + groovyScriptFileSuffix)

        /*
        version=1.0
        depends=script2:1.0,script3:1.0
        files=test.groovy
        */
        String packageFileName = '/package.properties'
        File packageFileLocal = new File(scriptTargetDir + packageFileName)

        if ((!f.exists() || !packageFileLocal.exists()) && !isRemote) {
            throw new TaskExecutionException(this, new RunScriptException('Target script file/package not found : ' + script + ':' + version))
        }

        if (isRemote) {
            // download first
            String remoteUrlDir = runScriptMirror + '/' + script + '/' + version

            PackageInfo packageInfoLocal
            PackageInfo packageInfoRemote

            if (packageFileLocal.exists()) {
                packageInfoLocal = getPackageInfo(packageFileLocal.text)
            }

            try {
                String xmlText = new URL(remoteUrlDir + packageFileName).text
                packageInfoRemote = getPackageInfo(xmlText)
                // just return local file when versions are same
                if (packageInfoLocal && packageInfoRemote.version == packageInfoLocal.version) {
                    getLogger().info('script use local : ' + script + ':' + version)
                    return f
                }
            } catch (Exception ex) {
                throw new TaskExecutionException(this, new RunScriptException('Download remote package file failed : ' + remoteUrlDir + packageFileName, ex))
            }

            if (packageInfoRemote.depends) {
                for (Map<String, String> depend in packageInfoRemote.depends) {
                    getScriptFile(runScriptMirror, depend.name, depend.version)
                }
            }

            for (file in packageInfoRemote.files) {
                downloadFile(file, scriptTargetDir, remoteUrlDir)
                getLogger().info('script download done : ' + file)
            }
        }

        return f
    }

    private class PackageInfo {
        List<Map<String, String>> depends
        List<String> files

        String version

        public String toString() {
            version + ' , ' + files + ' , ' + depends
        }
    }
}